#include "can.h"
#include "php.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#define CAN_DEVICE_NAME_LENGTH 32
char g_can_device[CAN_DEVICE_NAME_LENGTH];

unsigned char asc2nibble(char c) {

	if ((c >= '0') && (c <= '9'))
		return c - '0';

	if ((c >= 'A') && (c <= 'F'))
		return c - 'A' + 10;

	if ((c >= 'a') && (c <= 'f'))
		return c - 'a' + 10;

	return 16; /* error */
}


void can_minit(int module_number)
{
//  #define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
//  #define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
//  #define CAN_ERR_FLAG 0x20000000U /* error message frame */
//
//  /* valid bits in CAN ID for frame formats */
//  #define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */
//  #define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */
//  #define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */

    REGISTER_LONG_CONSTANT("CAN_EFF_FLAG",  0x80000000U, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("CAN_RTR_FLAG",  0x40000000U, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("CAN_ERR_FLAG",  0x20000000U, CONST_CS | CONST_PERSISTENT);

    REGISTER_LONG_CONSTANT("CAN_SFF_MASK",  0x000007FFU, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("CAN_EFF_MASK",  0x1FFFFFFFU, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("CAN_ERR_MASK",  0x1FFFFFFFU, CONST_CS | CONST_PERSISTENT);

    REGISTER_LONG_CONSTANT("CAN_TYPE_STANDARD", CAN_TYPE_STANDARD, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("CAN_TYPE_EXTEND", CAN_TYPE_EXTEND, CONST_CS | CONST_PERSISTENT);
}




PHP_FUNCTION(can_init)
{
    zend_string *can_device;
    zend_long bit_rate = 125000;
    char cmd[128];
    int ret = -1;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_STR(can_device)
        Z_PARAM_LONG(bit_rate)
    ZEND_PARSE_PARAMETERS_END();

    if((zend_long)ZSTR_LEN(can_device) <= 0)
    {
        php_printf("can_device required");

        RETURN_LONG(-1);
        return;
    }

    memset(cmd, '\0', SYSTEM_CMD_STR_LEN);
    strcat(cmd, can_device);

    sprintf(cmd, "%s%s%d", "/sbin/ip link set ", ZSTR_VAL(can_device), " type can bitrate ", bit_rate);
    ret = system(cmd);
    //    system("ip link set can0 type can bit_rate 100000");

    if( ret )
    {
        strcat(cmd, " execute error!");
        php_printf(cmd);
        RETURN_LONG(ret);
    }

    memset(cmd, '\0', SYSTEM_CMD_STR_LEN);
    //    system("ifconfig can0 up");
    sprintf("/sbin/ifconfig %s up", ZSTR_VAL(can_device));


    ret = system(cmd);

    if( ret )
    {
        strcat(cmd, " execute error!");
        php_printf(cmd);
        RETURN_LONG(ret);
        return;
    }

    RETURN_LONG(ret);
}


PHP_FUNCTION(can_uninit)
{
    char cmd[128];

    zend_string *can_device;
    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_STR(can_device)
    ZEND_PARSE_PARAMETERS_END();

    if((zend_long)ZSTR_LEN(can_device) <= 0)
    {
        php_printf("can_device required");

        RETURN_LONG(-1);

        return;
    }

    memset(cmd, '\0', SYSTEM_CMD_STR_LEN);
    sprintf(cmd, "%s", "/sbin/ifconfig %s down", ZSTR_VAL(can_device));

    RETURN_LONG(system(cmd));
}

PHP_FUNCTION(can_open)
{
    int ret;

    struct sockaddr_can addr;
    struct ifreq ifr;

    zend_string *can_device;

    struct can_filter rfilter[1];

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_STR(can_device)
    ZEND_PARSE_PARAMETERS_END();

    //1.Create socket
    int socket_id = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (socket_id < 0)
    {
        php_printf("socket PF_CAN failed");
        RETURN_LONG(-1);
    }

    //2.Specify can0 device
    strcpy(ifr.ifr_name, ZSTR_VAL(can_device));
    ret = ioctl(socket_id, SIOCGIFINDEX, &ifr);
    if (ret < 0)
    {
        php_printf("ioctl failed");
        RETURN_LONG(ret);

        return;
    }

    //php_printf("can_device: %s", ZSTR_VAL(can_device));
    //php_printf("can_socket: %d", can_socket);

    //3.Bind the socket to can0
    addr.can_family = PF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    ret = bind(socket_id, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        php_printf("bind failed");
        RETURN_LONG(ret);
        return;
    }

    memset(g_can_device, '\0', CAN_DEVICE_NAME_LENGTH);
    strcpy(g_can_device, ZSTR_VAL(can_device));
    RETURN_LONG(socket_id);
}


PHP_FUNCTION(can_send)
{
	zval *can_data_array, *entry, entry_n;
    zend_ulong index;
    zend_ulong can_id_type = 0;
    struct sockaddr_can addr;

    struct can_frame frame;
    zend_string* can_id;

    long ret = 0;
    long nbytes = -1;

    long socket_id = 0;
	ZEND_PARSE_PARAMETERS_START(4, 4)
        Z_PARAM_LONG(socket_id)
        Z_PARAM_STR(can_id)
	    Z_PARAM_LONG(can_id_type)
		Z_PARAM_ARRAY(can_data_array)
	ZEND_PARSE_PARAMETERS_END();

    if(socket_id < 0)
    {
        RETURN_LONG(-1);
        return;
    }

    long real_can_id = 0;
    int i;
    unsigned char tmp;

    char* c_can_id = ZSTR_VAL(can_id);
    //php_printf("c_can_id:%s\n", c_can_id);

    if( CAN_TYPE_STANDARD == can_id_type )
    {
        //php_printf("CAN_TYPE_EXTEND\n");
        for(i = 0; i < 3; ++i)
        {
            if((tmp = asc2nibble(c_can_id[i])) > 0x0F)
            {
                php_printf("can type extend error");
                RETURN_LONG(-1);
                return;
            }

            real_can_id |= (tmp << (2-i)*4);
        }

    }
    else
    {
        //php_printf("CAN_TYPE_STANDARD\n");
        for(i = 0; i < 8; i++)
        {
            if((tmp = asc2nibble(c_can_id[i])) > 0x0F)
            {
                php_printf("can type standard error");
                RETURN_LONG(-1);
                return;
            }

            real_can_id |= (tmp << (7-i)*4);

            if(!(real_can_id & CAN_ERR_FLAG ))
            {
                real_can_id |= CAN_EFF_FLAG;
            }
        }
    }

    frame.can_id = real_can_id;
    frame.can_dlc = zend_array_count(Z_ARRVAL_P(can_data_array));

    if( frame.can_dlc <= 0 )
    {
        php_printf("can data empty");
        RETURN_LONG(-1);
        return;
    }

    ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(can_data_array), index, entry) {
        if (Z_TYPE_P(entry) != IS_LONG) {
            zend_error(E_WARNING, "Expected integer for can data array " ZEND_ULONG_FMT, index);
            RETURN_LONG(-1);
            return;
        }

		//convert_scalar_to_number(&entry_n);
        frame.data[index] = Z_LVAL_P(entry);//ZVAL_LONG(entry_n);
    } ZEND_HASH_FOREACH_END();

    //4.Disable filtering rules, do not receive packets, only send
    setsockopt(socket_id, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

    //5.Set send data

    //php_printf("can_id  = 0x%X\r\n", frame.can_id);
    //php_printf("can_dlc = %d\r\n", frame.can_dlc);
//    int i = 0;
//    for(i = 0; i < 8; i++)
//    {
//        php_printf("data[%d] = %d\r\n", i, frame.data[i]);
//    }

    //6.Send message
    nbytes = write(socket_id, &frame, sizeof(frame));
    if(nbytes != sizeof(frame))
    {
        php_printf("Send Error frame[0]!\r\n");

        RETURN_LONG(-1);
        return;
    }

    RETURN_LONG(nbytes);
}

PHP_FUNCTION(can_read)
{
    int i = 0;
    zval* ret;
    zval tmp;
    //zval* can_data;
    HashTable* can_data;
    zend_long socket_id = -1;
    int nbytes = 0;
    struct can_frame frame;
    int index;
    char *p_str_ret = NULL;
    int len = 0;
    int can_id = 0;

    p_str_ret = (char*)emalloc(128);
    memset(p_str_ret, '\0', 128);

    //char str_ret[128];

	ZEND_PARSE_PARAMETERS_START(1, 1)
	    Z_PARAM_LONG(socket_id)
	ZEND_PARSE_PARAMETERS_END();

    strcpy(p_str_ret, g_can_device);

    if( socket_id < 0 )
    {
        php_printf("socket_id error");
    }

    memset(&frame, 0, sizeof(struct can_frame));

    //4.Define receive rules
    struct can_filter rfilter[1];
    rfilter[0].can_mask = CAN_EFF_MASK;
    setsockopt(socket_id, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

    //5.Receive data and exit
    ALLOC_HASHTABLE(ret);
    zend_hash_init(ret, 2, NULL, NULL, 0);
    while(1)
    {
        php_printf("waiting for can data...\n");
        nbytes = read(socket_id, &frame, sizeof(struct can_frame));
        if(nbytes > 0)
        {
            php_printf("\ngot data\n");

            ZVAL_LONG(&tmp, frame.can_id);
            zend_hash_str_add(ret, "id", strlen("id"), &tmp);
            ZVAL_LONG(&tmp, 9999);
            zend_hash_str_add(ret, "data", strlen("data"), &tmp);

            if( (frame.can_id & 0x80000000) == 0x80000000)
            {
                can_id = frame.can_id;
                can_id &= (~(1<<31));
            }
            else
            {
                can_id = frame.can_id;
            }
            len = strlen(p_str_ret);

            sprintf(p_str_ret+len, "  %08X   [%d] ", can_id, frame.can_dlc);
            len = strlen(p_str_ret);

            //sprintf(p_str_ret+len, " %02X", frame.data[0]);
            sprintf(p_str_ret+len, " %02X %02X %02X %02X %02X %02X %02X %02X", frame.data[0], frame.data[1], frame.data[2], frame.data[3], frame.data[4], frame.data[5], frame.data[6], frame.data[7]);
            //for(i = 0; i < frame.can_dlc; i++)
            php_printf("%s\n", p_str_ret);
            efree(p_str_ret);

            /*
            ALLOC_HASHTABLE(can_data);
            zend_hash_init(can_data, frame.can_dlc, NULL, NULL, 0);

            strcpy(str, "  ");
            php_printf("%s\n", str);

            sprintf(str+strlen(g_can_device) + 2, "%X", frame.can_id);
            */


            //int index = strlen(str);
          //  index = strlen(p_str_ret);

            /*
            php_printf("str=%s\n", str);
            ZVAL_LONG(&tmp, frame.data[0]);
            zend_hash_index_add(can_data, 0, &tmp);

            ZVAL_LONG(&tmp, 9998);
            zend_hash_str_add(ret, "data1", strlen("data1"), &tmp);

            zend_hash_str_add(ret, "data2", strlen("data2"), can_data);
            */
            //zval_ptr_dtor(can_data);
            //RETURN_ZVAL(ret, 0, 1);
            RETURN_ARR(ret);
        }
    }

    RETURN_NULL();
}


PHP_FUNCTION(can_close)
{
    zend_long socket_id = -1;

	ZEND_PARSE_PARAMETERS_START(0, 1)
		Z_PARAM_OPTIONAL
	    Z_PARAM_LONG(socket_id)
	ZEND_PARSE_PARAMETERS_END();

    if(socket_id > 0)
    {
        close(socket_id);
    }
}